// 《围棋》作者版权所有。保留所有权利。
// 此源代码的使用受BSD样式
// 许可证的约束，该许可证可以在许可证文件中找到。

package fuzz

import (
	"fmt"
	"io/ioutil"
	"os"
	"unsafe"
)

// sharedMem管理对从文件映射的虚拟内存区域的访问，
// 在多个进程之间共享。该区域包括用于标头的空间和
// 可变长度的值。
// 
// 模糊时，协调器从临时文件中为每个工作人员创建一个sharedMem。该缓冲区用于在进程之间将值传递给fuzz。
// 必须小心管理跨进程对共享内存的访问；
// sharedMem本身不提供同步。有关
// 的说明，请参见workerComm。
type sharedMem struct {
	// f是映射到内存中的文件。
	f *os.File

	// 区域是f的虚拟内存的映射区域。f的内容可以通过该片读取或写入。
	region []byte

	// 如果文件应在关闭时删除，则removeOnClose为真。
	removeOnClose bool

	// sys包含特定于操作系统的信息。
	sys sharedMemSys
}

// sharedMemHeader将元数据存储在共享内存中。
type sharedMemHeader struct {
	// count是工作者调用fuzz函数的次数。
	// 可由协调器重置。
	count int64

	// valueLen是区域中应读取的字节数。
	valueLen int

	// randState和randInc持有伪随机数生成器的状态。
	randState, randInc uint64

	// 如果区域包含原始字节，则rawInMem为真，这发生在
	// 最小化期间。如果在最小化过程中辅助进程失败后为true，则此
	// 表示发生了不可恢复的错误，该区域可以是
	// 用于检索导致错误的原始字节。返回共享内存缓冲区所需的大小，该缓冲区可以包含给定大小的值。
	rawInMem bool
}

func sharedMemSize(valueSize int) int {
	// TODO（jayconrod）：为每个平台设置合理的最大尺寸。
	return int(unsafe.Sizeof(sharedMemHeader{})) + valueSize
}

// sharedMemTempFile创建一个给定大小的新临时文件，然后将其映射到内存中。调用Close方法时，文件将被删除。
func sharedMemTempFile(size int) (m *sharedMem, err error) {
	// 创建一个临时文件。
	f, err := ioutil.TempFile("", "fuzz-*")
	if err != nil {
		return nil, err
	}
	defer func() {
		if err != nil {
			f.Close()
			os.Remove(f.Name())
		}
	}()

	// 将其调整为正确的大小。
	totalSize := sharedMemSize(size)
	if err := f.Truncate(int64(totalSize)); err != nil {
		return nil, err
	}

	// 将文件映射到内存中。
	removeOnClose := true
	return sharedMemMapFile(f, totalSize, removeOnClose)
}

// header返回指向共享内存区域内元数据的指针。
func (m *sharedMem) header() *sharedMemHeader {
	return (*sharedMemHeader)(unsafe.Pointer(&m.region[0]))
}

// valueRef返回当前存储在共享内存中的值。返回的
// 切片指向共享内存；它不是复制品。
func (m *sharedMem) valueRef() []byte {
	length := m.header().valueLen
	valueOffset := int(unsafe.Sizeof(sharedMemHeader{}))
	return m.region[valueOffset : valueOffset+length]
}

// valueCopy返回存储在共享内存中的值的副本。
func (m *sharedMem) valueCopy() []byte {
	ref := m.valueRef()
	b := make([]byte, len(ref))
	copy(b, ref)
	return b
}

// setValue将b中的数据复制到共享内存缓冲区，并设置
// 长度。len（b）必须小于或等于缓冲区
// /（由cap（m.value（））返回）的容量。
func (m *sharedMem) setValue(b []byte) {
	v := m.valueRef()
	if len(b) > cap(v) {
		panic(fmt.Sprintf("value length %d larger than shared memory capacity %d", len(b), cap(v)))
	}
	m.header().valueLen = len(b)
	copy(v[:cap(v)], b)
}

// setValueLen将valueRef 
// 返回的共享内存缓冲区的长度设置为n，最多可能是该片的上限。
// 
// 请注意，我们只能将长度存储在共享内存头中。完整的
// 片头包含一个指针，它可能只对一个进程有效，因为每个进程都可以将共享内存映射到不同的虚拟地址。
func (m *sharedMem) setValueLen(n int) {
	v := m.valueRef()
	if n > cap(v) {
		panic(fmt.Sprintf("length %d larger than shared memory capacity %d", n, cap(v)))
	}
	m.header().valueLen = n
}

// TODO（jayconrod）：添加调整缓冲区大小的方法。当
// 突变子可以增加输入长度时，我们需要它。只有协调员才能执行此操作，因为我们需要向工作人员发送一条消息，告诉其重新映射文件。
